/**
 * @ignore
 * filter dom tree to html string form,api designed by ckeditor
 * @author yiminghe@gmail.com
 */
var util = require('util');

/**
 * Filter for Html Parse Writer
 * @class KISSY.HtmlParser.Filter
 */
function Filter() {
    // {priority: ?, value:?}
    this.tagNames = [];
    this.attributeNames = [];
    this.tags = [];
    this.comment = [];
    this.text = [];
    this.cdata = [];
    this.attributes = [];
    this.root = [];
}

function findIndexToInsert(arr, p) {
    for (var i = 0; arr && i < arr.length; i++) {
        if (arr[i].priority > p) {
            return i;
        }
    }
    return arr.length;
}

function filterName(arr, v) {
    for (var i = 0; arr && i < arr.length; i++) {
        var items = arr[i].value;
        /*jshint loopfunc:true*/
        util.each(items, function (item) {
            v = v.replace(item[0], item[1]);
        });
    }
    return v;
}

function filterFn(arr, args, el) {
    var item, i, ret;
    for (i = 0; arr && i < arr.length; i++) {
        item = arr[i].value;
        if ((ret = item.apply(null, args)) === false) {
            return false;
        }
        // node can be replaced with another node
        if (el && ret && ret !== el) {
            // text filter can return string value directly
            if (typeof ret === 'string') {
                if (el.toHtml() === ret) {
                    return el;
                }
                el.nodeValue = ret;
                ret = el;
            }
            return this.onNode(ret);
        }
    }
    return el;
}

function filterAttr(arr, attrNode, el, _default) {
    for (var i = 0; arr && i < arr.length; i++) {
        var item = arr[i].value,
            ret,
            name = attrNode.name;
        if (item[name] && (ret = item[name].call(null, attrNode.value, el)) === false) {
            return ret;
        }
        // 2012.06.26 change attribute value
        if (typeof ret === 'string') {
            attrNode.value = ret;
        }
    }
    return _default;
}

Filter.prototype = {
    constructor: Filter,

    /**
     *
     * @param rules
     * {
         *   tagNames:[ [/^ke/,''] ],
         *   attributeNames:[[^on],''],
         *   tags:{
         *      p:function(element){},
         *      ^:function(element){},
         *      $:function(element){}
         *   }
         *   comment:function(){},
         *   attributes:{style:function(){}},
         *   text:function(){},
         *   root:function(){}
         * }
     * @param {Number} [priority] 值越小，优先级越高 ,最低 1
     */
    addRules: function (rules, priority) {
        priority = priority || 10;
        for (var r in rules) {

            var holder = this[r];
            if (holder) {
                var index = findIndexToInsert(holder, priority);
                holder.splice(index, 0, {
                    value: rules[r],
                    priority: priority
                });
            }

        }
    },

    /**
     * when encounter element name transformer ,directly transform
     * @param v
     */
    onTagName: function (v) {
        return filterName(this.tagNames, v);
    },

    onAttributeName: function (v) {
        return filterName(this.attributeNames, v);
    },

    onText: function (el) {
        return filterFn.call(this, this.text, [el.toHtml(), el], el);
    },

    onCData: function (el) {
        return filterFn.call(this, this.cdata, [el.toHtml(), el], el);
    },

    onAttribute: function (attrNode, el) {
        return filterAttr(this.attributes, attrNode, el, attrNode);
    },

    onComment: function (el) {
        return filterFn.call(this, this.comment, [el.toHtml(), el], el);
    },

    onNode: function (el) {
        var t = el.nodeType;
        if (t === 1) {
            return this.onTag(el);
        } else if (t === 3) {
            return this.onText(el);
        } else if (t === 8) {
            return this.onComment(el);
        }
    },

    onFragment: function (el) {
        return filterFn.call(this, this.root, [el], el);
    },

    onTag: function (el) {
        // ^ tagName $
        var filters = ['^', el.tagName, '$'],
            tags = this.tags,
            ret;
        for (var i = 0; i < filters.length; i++) {
            var filter = filters[i];
            for (var j = 0; j < tags.length; j++) {
                var element = tags[j].value;
                if (element[filter]) {
                    // node is removed with its children
                    if ((ret = element[filter](el)) === false) {
                        return false;
                    }
                    // node is replaced with another node
                    if (ret && ret !== el) {
                        return this.onNode(ret);
                    }
                    // node is removed (children preserved)
                    if (!el.tagName) {
                        return el;
                    }
                }
            }
        }
        return el;
    }

};

module.exports = Filter;